﻿using System;
using System.Reflection;
using System.Reflection.Emit;

namespace Gonte.Reflection.Emit
{
    public static class Emitter
    {
        static readonly Module ThisModule = typeof(Emitter).Module;
        static readonly Type[] OneObjectType = { typeof(Object) };
        static readonly Type[] TwoObjectTypes = { typeof(Object), typeof(Object) };

        //public static MethodHandler CreateMethodHandler(MethodBase method)
        //{
        //    var dynam = new DynamicMethod(string.Empty, typeof(object), ManyObjects, Module, true);
        //    ILGenerator generator = dynam.GetILGenerator();

        //    ParameterInfo[] args = method.GetParameters();

        //    Label argsOK = generator.DefineLabel();

        //    generator.Emit(OpCodes.Ldarg_1);
        //    generator.Emit(OpCodes.Ldlen);
        //    generator.Emit(OpCodes.Ldc_I4, args.Length);
        //    generator.Emit(OpCodes.Beq, argsOK);

        //    generator.Emit(OpCodes.Newobj, typeof(TargetParameterCountException).GetConstructor(Type.EmptyTypes));
        //    generator.Emit(OpCodes.Throw);

        //    generator.MarkLabel(argsOK);

        //    generator.PushInstance(method.DeclaringType);

        //    for(int i = 0; i < args.Length; i++)
        //    {
        //        generator.Emit(OpCodes.Ldarg_1);
        //        generator.Emit(OpCodes.Ldc_I4, i);
        //        generator.Emit(OpCodes.Ldelem_Ref);

        //        generator.UnboxIfNeeded(args[i].ParameterType);
        //    }

        //    if (method.IsConstructor)
        //        generator.Emit(OpCodes.Newobj, method as ConstructorInfo);
        //    else if (method.IsFinal || !method.IsVirtual)
        //        generator.Emit(OpCodes.Call, method as MethodInfo);
        //    else
        //        generator.Emit(OpCodes.Callvirt, method as MethodInfo);

        //    Type returnType = method.IsConstructor ? method.DeclaringType : (method as MethodInfo).ReturnType;
        //    if (returnType != typeof(void))
        //        generator.BoxIfNeeded(returnType);
        //    else
        //        generator.Emit(OpCodes.Ldnull);

        //    generator.Emit(OpCodes.Ret);

        //    return (MethodHandler)dynam.CreateDelegate(typeof(MethodHandler));
        //}

        #region Property accessors

        public static SetValueHandler EmitPropertySetter(PropertyInfo propertyInfo)
        {
            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(void), TwoObjectTypes, ThisModule, true);
            ILGenerator generator = dynamicMethod.GetILGenerator();
            MethodInfo methodInfo = propertyInfo.GetSetMethod();

            generator.Emit(OpCodes.Nop);
            if (!methodInfo.IsStatic)
            {
                PushInstance(generator, propertyInfo.DeclaringType);
            }

            generator.Emit(OpCodes.Ldarg_1);
            UnboxIfNeeded(generator, propertyInfo.PropertyType);
            CallMethod(generator, methodInfo);
            generator.Emit(OpCodes.Ret);

            return (SetValueHandler)dynamicMethod.CreateDelegate(typeof(SetValueHandler));
        }

        public static GetValueHandler EmitPropertyGetter(PropertyInfo propertyInfo)
        {
            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(Object), OneObjectType, ThisModule, true);
            ILGenerator generator = dynamicMethod.GetILGenerator();
            MethodInfo methodInfo = propertyInfo.GetGetMethod();

            if (!methodInfo.IsStatic)
            {
                PushInstance(generator, propertyInfo.DeclaringType);
            }

            CallMethod(generator, methodInfo);
            BoxIfNeeded(generator, propertyInfo.PropertyType);
            generator.Emit(OpCodes.Ret);

            return (GetValueHandler)dynamicMethod.CreateDelegate(typeof(GetValueHandler));
        }

        #endregion

        #region Field accessors

        public static SetValueHandler EmitFieldSetter(FieldInfo fieldInfo)
        {
            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(void), TwoObjectTypes, ThisModule, true);
            ILGenerator generator = dynamicMethod.GetILGenerator();

            if (!fieldInfo.IsStatic)
            {
                PushInstance(generator, fieldInfo.DeclaringType);
            }

            generator.Emit(OpCodes.Ldarg_1);
            UnboxIfNeeded(generator, fieldInfo.FieldType);
            generator.Emit(OpCodes.Stfld, fieldInfo);
            generator.Emit(OpCodes.Ret);

            return (SetValueHandler)dynamicMethod.CreateDelegate(typeof(SetValueHandler));
        }

        public static GetValueHandler CreateFieldGetterHandler(FieldInfo fieldInfo)
        {
            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), OneObjectType, ThisModule, true);
            ILGenerator generator = dynamicMethod.GetILGenerator();

            if (!fieldInfo.IsStatic)
            {
                PushInstance(generator, fieldInfo.DeclaringType);
            }

            generator.Emit(OpCodes.Ldfld, fieldInfo);
            BoxIfNeeded(generator, fieldInfo.FieldType);
            generator.Emit(OpCodes.Ret);

            return (GetValueHandler)dynamicMethod.CreateDelegate(typeof(GetValueHandler));
        } 

        #endregion

        //public static Func<object> CreateParameterlessConstructorHandler(Type type)
        //{
        //    var dynam = new DynamicMethod(string.Empty, typeof(object), Type.EmptyTypes, Module, true);
        //    ILGenerator generator = dynam.GetILGenerator();

        //    if (type.IsValueType)
        //    {
        //        generator.DeclareLocal(type);
        //        generator.Emit(OpCodes.Ldloc_0);
        //        generator.Emit(OpCodes.Box, type);
        //    }
        //    else
        //        generator.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));

        //    generator.Emit(OpCodes.Ret);

        //    return (Func<object>)dynam.CreateDelegate(typeof(Func<object>));
        //}

        public delegate Object GetValueHandler(Object @object);
        public delegate void SetValueHandler(Object @object, Object value);

        #region Private Helpers

        public static void PushInstance(ILGenerator generator, Type type)
        {
            generator.Emit(OpCodes.Ldarg_0);

            if (type.IsValueType)
            {
                generator.Emit(OpCodes.Unbox, type);
            }
        }

        public static void BoxIfNeeded(ILGenerator generator, Type type)
        {
            if (type.IsValueType)
            {
                generator.Emit(OpCodes.Box, type);
            }
        }

        public static void UnboxIfNeeded(ILGenerator generator, Type type)
        {
            if (type.IsValueType)
            {
                generator.Emit(OpCodes.Unbox_Any, type);
            }
        }

        public static void CallMethod(ILGenerator generator, MethodInfo methodInfo)
        {
            if (methodInfo.IsFinal
                || !methodInfo.IsVirtual)
            {
                generator.Emit(OpCodes.Call, methodInfo);
            }
            else
            {
                generator.Emit(OpCodes.Callvirt, methodInfo);
            }
        }

        #endregion
    }
}
